package com.lxj.xpopup.util;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Description: copy from https://github.com/Blankj/AndroidUtilCode
 */
public final class XPermission {

  private static List<String> PERMISSIONS;

  private static XPermission sInstance;
  private Context context;

  private OnRationaleListener mOnRationaleListener;
  private SimpleCallback mSimpleCallback;
  private FullCallback mFullCallback;
  private ThemeCallback mThemeCallback;
  private Set<String> mPermissions;
  private List<String> mPermissionsRequest;
  private List<String> mPermissionsGranted;
  private List<String> mPermissionsDenied;
  private List<String> mPermissionsDeniedForever;

  private static SimpleCallback sSimpleCallback4WriteSettings;
  private static SimpleCallback sSimpleCallback4DrawOverlays;

  /**
   * Return the permissions used in application.
   *
   * @return the permissions used in application
   */
  public List<String> getPermissions() {
    return getPermissions(context.getPackageName());
  }

  /**
   * Return the permissions used in application.
   *
   * @param packageName The name of the package.
   * @return the permissions used in application
   */
  public List<String> getPermissions(final String packageName) {
    PackageManager pm = context.getPackageManager();
    try {
      String[] permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
          .requestedPermissions;
      if (permissions == null) {
        return Collections.emptyList();
      }
      return Arrays.asList(permissions);
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
      return Collections.emptyList();
    }
  }

  /**
   * Return whether <em>you</em> have been granted the permissions.
   *
   * @param permissions The permissions.
   * @return {@code true}: yes<br>{@code false}: no
   */
  public boolean isGranted(final String... permissions) {
    for (String permission : permissions) {
      if (!isGranted(permission)) {
        return false;
      }
    }
    return true;
  }

  private boolean isGranted(final String permission) {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
        || PackageManager.PERMISSION_GRANTED
        == ContextCompat.checkSelfPermission(context, permission);
  }

  /**
   * Return whether the app can modify system settings.
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  @RequiresApi(api = Build.VERSION_CODES.M)
  public boolean isGrantedWriteSettings() {
    return Settings.System.canWrite(context);
  }

  @RequiresApi(api = Build.VERSION_CODES.M)
  public void requestWriteSettings(final SimpleCallback callback) {
    if (isGrantedWriteSettings()) {
      if (callback != null) callback.onGranted();
      return;
    }
    sSimpleCallback4WriteSettings = callback;
    PermissionActivity.start(context, PermissionActivity.TYPE_WRITE_SETTINGS);
  }

  @TargetApi(Build.VERSION_CODES.M)
  private void startWriteSettingsActivity(final Activity activity, final int requestCode) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + context.getPackageName()));
    if (!isIntentAvailable(intent)) {
      launchAppDetailsSettings();
      return;
    }
    activity.startActivityForResult(intent, requestCode);
  }

  /**
   * Return whether the app can draw on top of other apps.
   *
   * @return {@code true}: yes<br>{@code false}: no
   */
  public boolean isGrantedDrawOverlays() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      return Settings.canDrawOverlays(context);
    } else return true;
  }

  @RequiresApi(api = Build.VERSION_CODES.M)
  public void requestDrawOverlays(final SimpleCallback callback) {
    if (isGrantedDrawOverlays()) {
      if (callback != null) callback.onGranted();
      return;
    }
    sSimpleCallback4DrawOverlays = callback;
    PermissionActivity.start(context, PermissionActivity.TYPE_DRAW_OVERLAYS);
  }

  @TargetApi(Build.VERSION_CODES.M)
  private void startOverlayPermissionActivity(final Activity activity, final int requestCode) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
    intent.setData(Uri.parse("package:" + context.getPackageName()));
    if (!isIntentAvailable(intent)) {
      launchAppDetailsSettings();
      return;
    }
    activity.startActivityForResult(intent, requestCode);
  }

  /**
   * Launch the application's details settings.
   */
  public void launchAppDetailsSettings() {
    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    intent.setData(Uri.parse("package:" + context.getPackageName()));
    if (!isIntentAvailable(intent)) return;
    context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
  }

  /**
   * Set the permissions.
   *
   * @param permissions The permissions.
   * @return the single {@link XPermission} instance
   */
  public static XPermission create(Context context, @PermissionConstants.PermissionGroup final String... permissions) {
    if (sInstance == null) return new XPermission(context, permissions);
    sInstance.context = context;
    sInstance.prepare(permissions);
    return sInstance;
  }

  public static XPermission getInstance() {
    return sInstance;
  }

  private boolean isIntentAvailable(final Intent intent) {
    return context
        .getPackageManager()
        .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
        .size() > 0;
  }

  private XPermission(Context ctx, final String... permissions) {
    sInstance = this;
    context = ctx;
    prepare(permissions);
  }

  private void prepare(final String... permissions) {
    mPermissions = new LinkedHashSet<>();
    PERMISSIONS = getPermissions();
    if (permissions == null) return;
    for (String permission : permissions) {
      for (String aPermission : PermissionConstants.getPermissions(permission)) {
        if (PERMISSIONS.contains(aPermission)) {
          mPermissions.add(aPermission);
        }
      }
    }
  }

  /**
   * Set rationale listener.
   *
   * @param listener The rationale listener.
   */
  public XPermission rationale(final OnRationaleListener listener) {
    mOnRationaleListener = listener;
    return this;
  }

  /**
   * Set the simple call back.
   *
   * @param callback the simple call back
   */
  public XPermission callback(final SimpleCallback callback) {
    mSimpleCallback = callback;
    return this;
  }

  /**
   * Set the full call back.
   *
   * @param callback the full call back
   */
  public XPermission callback(final FullCallback callback) {
    mFullCallback = callback;
    return this;
  }

  /**
   * Start request.
   */
  public void request() {
    mPermissionsGranted = new ArrayList<>();
    mPermissionsRequest = new ArrayList<>();
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
      mPermissionsGranted.addAll(mPermissions);
      requestCallback();
    } else {
      for (String permission : mPermissions) {
        if (isGranted(permission)) {
          mPermissionsGranted.add(permission);
        } else {
          mPermissionsRequest.add(permission);
        }
      }
      if (mPermissionsRequest.isEmpty()) {
        requestCallback();
      } else {
        startPermissionActivity();
      }
    }
  }

  public void releaseContext() {
    context = null;
  }

  @RequiresApi(api = Build.VERSION_CODES.M)
  private void startPermissionActivity() {
    mPermissionsDenied = new ArrayList<>();
    mPermissionsDeniedForever = new ArrayList<>();
    PermissionActivity.start(context, PermissionActivity.TYPE_RUNTIME);
  }

  @RequiresApi(api = Build.VERSION_CODES.M)
  private boolean rationale(final Activity activity) {
    boolean isRationale = false;
    if (mOnRationaleListener != null) {
      for (String permission : mPermissionsRequest) {
        if (activity.shouldShowRequestPermissionRationale(permission)) {
          getPermissionsStatus(activity);
          mOnRationaleListener.rationale(new OnRationaleListener.ShouldRequest() {
            @Override
            public void again(boolean again) {
              if (again) {
                startPermissionActivity();
              } else {
                requestCallback();
              }
            }
          });
          isRationale = true;
          break;
        }
      }
      mOnRationaleListener = null;
    }
    return isRationale;
  }

  private void getPermissionsStatus(final Activity activity) {
    for (String permission : mPermissionsRequest) {
      if (isGranted(permission)) {
        mPermissionsGranted.add(permission);
      } else {
        mPermissionsDenied.add(permission);
        if (!activity.shouldShowRequestPermissionRationale(permission)) {
          mPermissionsDeniedForever.add(permission);
        }
      }
    }
  }

  private void requestCallback() {
    if (mSimpleCallback != null) {
      if (mPermissionsRequest.size() == 0
          || mPermissions.size() == mPermissionsGranted.size()) {
        mSimpleCallback.onGranted();
      } else {
        if (!mPermissionsDenied.isEmpty()) {
          mSimpleCallback.onDenied();
        }
      }
      mSimpleCallback = null;
    }
    if (mFullCallback != null) {
      if (mPermissionsRequest.size() == 0
          || mPermissions.size() == mPermissionsGranted.size()) {
        mFullCallback.onGranted(mPermissionsGranted);
      } else {
        if (!mPermissionsDenied.isEmpty()) {
          mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied);
        }
      }
      mFullCallback = null;
    }
    mOnRationaleListener = null;
    mThemeCallback = null;
  }

  private void onRequestPermissionsResult(final Activity activity) {
    getPermissionsStatus(activity);
    requestCallback();
  }


  @RequiresApi(api = Build.VERSION_CODES.M)
  public static class PermissionActivity extends Activity {

    private static final String TYPE = "TYPE";
    public static final int TYPE_RUNTIME = 0x01;
    public static final int TYPE_WRITE_SETTINGS = 0x02;
    public static final int TYPE_DRAW_OVERLAYS = 0x03;

    public static void start(final Context context, int type) {
      Intent starter = new Intent(context, PermissionActivity.class);
      starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      starter.putExtra(TYPE, type);
      context.startActivity(starter);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
      getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
          | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
          | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
      getWindow().getAttributes().alpha = 0;
      int byteExtra = getIntent().getIntExtra(TYPE, TYPE_RUNTIME);
      if (byteExtra == TYPE_RUNTIME) {
        if (sInstance == null) {
          super.onCreate(savedInstanceState);
          Log.e("XPermission", "request permissions failed");
          finish();
          return;
        }
        if (sInstance.mThemeCallback != null) {
          sInstance.mThemeCallback.onActivityCreate(this);
        }
        super.onCreate(savedInstanceState);

        if (sInstance.rationale(this)) {
          finish();
          return;
        }
        if (sInstance.mPermissionsRequest != null) {
          int size = sInstance.mPermissionsRequest.size();
          if (size <= 0) {
            finish();
            return;
          }
          requestPermissions(sInstance.mPermissionsRequest.toArray(new String[size]), 1);
        }
      } else if (byteExtra == TYPE_WRITE_SETTINGS) {
        super.onCreate(savedInstanceState);
        sInstance.startWriteSettingsActivity(this, TYPE_WRITE_SETTINGS);
      } else if (byteExtra == TYPE_DRAW_OVERLAYS) {
        super.onCreate(savedInstanceState);
        sInstance.startOverlayPermissionActivity(this, TYPE_DRAW_OVERLAYS);
      }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
      sInstance.onRequestPermissionsResult(this);
      finish();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      finish();
      return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == TYPE_WRITE_SETTINGS) {
        if (sSimpleCallback4WriteSettings == null) return;
        if (sInstance.isGrantedWriteSettings()) {
          sSimpleCallback4WriteSettings.onGranted();
        } else {
          sSimpleCallback4WriteSettings.onDenied();
        }
        sSimpleCallback4WriteSettings = null;
      } else if (requestCode == TYPE_DRAW_OVERLAYS) {
        if (sSimpleCallback4DrawOverlays == null) return;
        if (sInstance.isGrantedDrawOverlays()) {
          sSimpleCallback4DrawOverlays.onGranted();
        } else {
          sSimpleCallback4DrawOverlays.onDenied();
        }
        sSimpleCallback4DrawOverlays = null;
      }
      finish();
    }
  }

  public interface OnRationaleListener {

    void rationale(ShouldRequest shouldRequest);

    interface ShouldRequest {
      void again(boolean again);
    }
  }

  public interface SimpleCallback {
    void onGranted();

    void onDenied();
  }

  public interface FullCallback {
    void onGranted(List<String> permissionsGranted);

    void onDenied(List<String> permissionsDeniedForever, List<String> permissionsDenied);
  }

  public interface ThemeCallback {
    void onActivityCreate(Activity activity);
  }
}